{*************************************************************************

Hierarchical Menu Example

This program is a simple example of how to add hierarchical menus to your
application. A few tips:

The toughest part is defining the menu item that "owns" the submenu. The
item must be defined with a command-key of $1B, and a "mark" character
whose value is the ID of the submenu. The submenu is defined just like
a normal menu, but is inserted in the Menu Manager's private "hierarchical"
menu list by specifying -1 as the second parameter to InsertMenu. 

It's easiest to declare all your menus (and submenus) as resources (as
opposed to creating them at runtime with NewMenu), even if you're creating
a submenu that will initially contain no items (like the Font submenu in 
this example). This simplifies your program's handling of submenus (note
that in this example, since the submenus' IDs follow the regular menu's
ID, the only special handling of submenus is the test (in SetUpMenus) to
decide whether to use 0 or -1 as the second parameter to InsertMenu!).

Submenus owned by applications' menus' IDs should always be in the range
1 through 235 (inclusive); desk accessories should use IDs 236 through
255, and should leave their menus and submenus in the menu bar only when
they're active (that is, call InsertMenu when handling an activate event,
and DeleteMenu when handling a deactivate event). This really applies to 
all menus; it's most important for pop-up and submenus, however. All this
to prevent conflicts (it may be helpful to note that in searching through 
the menu list, the hierarchical list [containing all submenus & pop-up
menus] is searched first).

This particular example is meant to show the mechanics of creating and
handling events from submenus; in the interest of simplicity, certain nice
features (like checkmarking the current font and styles, setting the 
style of "real" font sizes in the Font submenu to "outline", etc) have been
omitted.

Have fun.

Note: the current version does not actually call SysEnvirons to
determine whether System 4.1 is running. This will be fixed, but
in the meantime, run this with System 
*************************************************************************}

PROGRAM HierMenus;
{*
 * Hierarchical Menu Example
 * Bryan Stearns 05May87 
 *
 * (C) 1987 Apple Computer, Inc.
 * All Rights Reserved.
 *}

USES MemTypes, Quickdraw, OSIntf, ToolIntf, PackIntf;

{$R-} {no range checking}
{$D+} {Generate debug symbols}

CONST
	menuBase = 128;			{we number our menus starting at 128}
	
	mainMenu = menuBase;	{main menu ID}
		{we don't care about the Font/Style/Size item numbers; we}
		{deal with choices in the submenus directly}
		quitItem = 5;			{Quit item number}
	
	{the Font submenu ID}
	fontMenu = mainMenu+1;		
		{we retrieve the font name from the menu by item number,}
		{then use GetFNum(the font name) to convert to family}
		{number. The font Name should be stored with the document,}
		{not the family number (because Font/DA Mover renumbers)}
		
	{the Style submenu ID}
	styleMenu = fontMenu+1;		
		{Styles are dealt with in the same order they appear in}
		{the Quickdraw "Style" type. Sneaky, but consistent with}
		{other Macintosh applications}
		
	{the Size submenu ID}
	sizeMenu = styleMenu+1;		 {the Size submenu ID}
		{Sizes are retrieved as text from the menu item; this would}
		{allows advanced users to edit the menu to add custom sizes}
		
	lastMenu = sizeMenu;		{the last menu resource}
	firstSubMenu = fontMenu; 	{the first submenu - see SetUpMenus}
	
	myWINDid = 128;			{our window template}
	myALRTid = 128; 		{our Òneed right machine & sys softwareÓ alert}
	mySTRListID = 128;		{our string list}
	
VAR	myWindow: WindowPtr;		{our window pointer}
	myEvent: EventRecord;		{Event record for GetNextEvent}	
	myMenus: ARRAY [menuBase..lastMenu] OF MenuHandle; {our menus}
	done: BOOLEAN;				{a flag that the user chose Quit}
	dragRect: Rect;				{drag limit rectangle, for DragWindow}
	
	demoStr1, demoStr2,			{Strings to draw in our demo window}
	demoStr3, demoStr4: Str255;	
	
	curFont: INTEGER;			{the current font number}
	curSize: INTEGER;
	curStyle: Style;
	
	i: INTEGER;					{a temp}
	mResult: LongInt;			{temp, result from MenuSelect}
	
	
{*
 * Once-only initialization for my menus
 * (Thanks, Andy!)
 *}
PROCEDURE SetUpMenus;
VAR i: INTEGER;
BEGIN
	{Get and insert each menu. The normal menus are inserted using zero}
	{as the second parameter to InsertMenu; the submenus get inserted using}
	{-1, to tell the Menu Manager to insert in the hierarchical menu list}
	FOR i := menuBase TO lastMenu DO BEGIN
		myMenus[i] := GetMenu(i); {get the menu from the resource}
		
		{insert it into a menu bar, one way or another}
		IF i < firstSubMenu THEN
			{it's a regular menu-bar menu}
			InsertMenu(myMenus[i],0) {insert at end of normal menu list}
		ELSE 
			{it's a submenu}
			InsertMenu(myMenus[i],-1); {insert hierarchical submenu}
	END; {for each menu}
	
	{Add the fonts to the Font submenu}
	AddResMenu(myMenus[fontMenu],'FONT'); {add fonts to the font menu}
	
	DrawMenuBar; {draw our menus' titles}
END; {setupmenus}


{*
 * Handle a command, either from a command key or from
 * a menu choice. MResult is the value returned by 
 * MenuSelect or MenuKey.
 *}
PROCEDURE DoCommand (mResult: LONGINT);
VAR theItem,theMenu: INTEGER;
	tmpStr: Str255;
	tmpLong: LongInt;
BEGIN
	theItem := LoWord(mResult); {extract the menu and item numbers}
	theMenu := HiWord(mResult);    
	CASE theMenu OF {which menu?}
		mainMenu:  CASE theItem OF
			{Note that we don't have any cases for the font, style, or}
			{size menus here: those are handled independently, below. Only}
			{normal items that appear in this menu are taken care of here.}
			quitItem: done := TRUE; 
		END; {mainMenu case}

		fontMenu: BEGIN
			{Here, we just change a global font number. However, if your}
			{application saves font information in its documents, you should}
			{save the font name (rather than the number), to avoid remapping}
			{because of Font/DA Mover number changes. Each time you open}
			{such a document, use GetFNum to find the right "current number".}
			GetItem(myMenus[fontMenu],theItem,tmpStr); {get the chosen fontÕs name}
			GetFNum(tmpStr,curFont); {get the new fontÕs number, to our global}
			InvalRect(thePort^.portRect); {remember that we need to redraw}
		END; {fontMenu case}

		styleMenu: BEGIN
			{The styles (other than Plain) are arranged in the}
			{menu in the same order that they appear in the}
			{Style type. If it's not plain, we can convert the}
			{item number to the proper enumerated value for the style}
			{and use set math to toggle the particular style}
			{characteristic.}
			IF theItem = 1 THEN curStyle := [] ELSE BEGIN {not 'Plain'}
				{it's not plain, so toggle this style characteristic}
				IF StyleItem(theItem-2) IN curStyle THEN
					{this style characteristic is currently on, so turn it off}
					curStyle := curStyle - [StyleItem(theItem-2)]
				ELSE
					{this style characteristic is currently on, so turn it off}
					curStyle := curStyle + [StyleItem(theItem-2)];
			END;
			InvalRect(thePort^.portRect); {remember that we need to redraw}
		END; {styleMenu case}

		sizeMenu: BEGIN
			{Read the size out of the menu item}
			GetItem(myMenus[sizeMenu],theItem,tmpStr); {get the chosen string}
			StringToNum(tmpStr,tmpLong); {convert to longint}
			curSize := tmpLong; {convert it to integer}
			InvalRect(thePort^.portRect); {remember that we need to redraw}
		END; {sizeMenu case}
		
  	END; {menu case}          
	IF NOT done THEN HiliteMenu(0); {un-hilight the chosen menu item}
END; {DoCommand}


{*
 * Main event loop
 *}
PROCEDURE MainEventLoop;
VAR thePart, theHeight: INTEGER;
	whichWindow: WindowPtr;
	theChar: CHAR;
	dragRect: Rect;
	fInfo: FontInfo;
BEGIN
	done := FALSE; {we're not through yet!}
	dragRect := screenBits.bounds;
	
	{Main event loop}
	REPEAT  
		SystemTask; {let the desk accs run}
		IF GetNextEvent(everyEvent,myEvent) THEN WITH myEvent DO BEGIN
			CASE what OF 
				mouseDown: BEGIN {"Click"}
					thePart := FindWindow(where,whichWindow);
					CASE thePart OF
						inSysWindow: SystemClick(myEvent,whichWindow);
						inMenuBar: DoCommand(MenuSelect(myEvent.where)); {nothing special here!}
						inDrag:	DragWindow(whichWindow,myEvent.where,dragRect);
					END; {case}
				END; {mouseDown}

				keyDown, autoKey: BEGIN
					theChar := CHR(BitAnd(myEvent.message,charCodeMask));  {get the char}
					IF BitAnd(myEvent.modifiers,cmdKey) <> 0 THEN BEGIN
						DoCommand(MenuKey(theChar)); {pass it to the command handler}
					END ELSE BEGIN {not a command key}
						{ignore it}
					END; {if not command key}
				END; {keydown}

				updateEvt: BEGIN {our window needs drawing}
					BeginUpdate(Windowptr(message));
					
					SetPort(myWindow); {make sure we're in our port}
					EraseRect(thePort^.portRect); {erase the old stuff}
					
					TextFont(curFont); {Set the right font/size/style}
					TextSize(curSize);
					TextFace(curStyle);
					
					GetFontInfo(fInfo);
					WITH fInfo DO theHeight := ascent+descent+leading;
					MoveTo(20,20+theHeight); DrawString(demoStr1);
					MoveTo(20,20+(theHeight*2)); DrawString(demoStr2);
					MoveTo(20,20+(theHeight*3)); DrawString(demoStr3);
					MoveTo(20,20+(theHeight*5)); DrawString(demoStr4);
										
					EndUpdate(WindowPtr(message));
				END;
			END; {case}
		END; {if we got an event}
		
	UNTIL done; {keep it up until the user quits}
	
END; {maineventloop}
 
 
{*
 * Main
 *}
BEGIN
	{Initialize all the usual managers}
	InitGraf(@thePort);	
	InitFonts;			
	FlushEvents(everyEvent,0);
	InitWindows;				
	InitMenus;			
	TEInit;
	InitDialogs(NIL);			
	InitCursor;			
	
	{Check to make sure weÕre running on a machine}
	{capable of supporting hierarchical menus; clearly,}
	{we must do this before calling SetUpMenus!}
	(** SysEnvirons(xxx); **)
	IF FALSE THEN BEGIN {Sorry, see your dealer}
		i := StopAlert(myALRTid,NIL); {put up the alert}
		ExitToShell; {back to the finder}
	END;
	
	curFont := 1; {Application font}
	curSize := 0; {default size}
	curStyle := []; {plain}
	
	SetUpMenus; {the oldest Macintosh subroutine!}
	
	{read the strings that we draw in our window}
	{from a STR# resource}
	GetIndString(demoStr1,mySTRListID,1);
	GetIndString(demoStr2,mySTRListID,2);
	GetIndString(demoStr3,mySTRListID,3);
	GetIndString(demoStr4,mySTRListID,4);
	
	{Get our window}
	myWindow := GetNewWindow(myWINDid,NIL,POINTER(-1));

	MainEventLoop; {handle events until the user quits}
END.


